package com.anvo.demo

import android.media.*
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.WorkerThread
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import com.anvo.demo.ui.theme.SoundTouchAndroidTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import net.surina.soundtouch.SoundTouch
import java.nio.ByteOrder

class MainActivity : ComponentActivity() {

    companion object {
        private const val TAG = "MainActivity"
    }

    private var prePlayedFrameTotal = 0L
    private var seekToProgress = -1L
    private var startCount = 0
    private val uriFlow = MutableStateFlow<Uri?>(null)
    private val isPlayingFlow = MutableStateFlow(false)
    private val pitchFlow = MutableStateFlow(0f)
    private val frameCountFlow = MutableStateFlow(0L)
    private val progressFlow = MutableStateFlow(0L)
    private val durationFlow = MutableStateFlow(0L)
    private val singerFlow = MutableStateFlow("")
    private val songNameFlow = MutableStateFlow("")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            SoundTouchAndroidTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    val uri by uriFlow.collectAsState()
                    if (uri == null) {
                        val context = LocalContext.current
                        val launcher =
                            rememberLauncherForActivityResult(
                                contract = ActivityResultContracts.GetContent(),
                            ) {
                                it ?: return@rememberLauncherForActivityResult
                                uriFlow.value = it
                                MediaMetadataRetriever().run {
                                    setDataSource(context, it)
                                    singerFlow.value =
                                        extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST).orEmpty()
                                    songNameFlow.value =
                                        extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE).orEmpty()
                                    release()
                                }
                            }
                        Button(onClick = {
                            launcher.launch("audio/*")
                        }, modifier = Modifier.padding(innerPadding)) {
                            Text("选择要播放的音乐")
                        }
                        return@Scaffold
                    }
                    val pitch by pitchFlow.collectAsState()
                    val frameCount by frameCountFlow.collectAsState()
                    val progress by progressFlow.collectAsState()
                    val duration by durationFlow.collectAsState()
                    val isPlaying by isPlayingFlow.collectAsState()
                    val singer by singerFlow.collectAsState()
                    val songName by songNameFlow.collectAsState()

                    Greeting(
                        modifier = Modifier.padding(innerPadding),
                        singer = singer,
                        songName = songName,
                        isPlaying = isPlaying,
                        frameCount = frameCount,
                        progress = progress,
                        duration = duration,
                        pitch = pitch,
                        pitchChanged = {
                            pitchFlow.value = it
                        },
                        start = {
                            play()
                        },
                        stop = {
                            isPlayingFlow.value = false
                        },
                        reset = {
                            reset()
                        },
                        seekTo = {
                            seekToProgress = it
                            progressFlow.value = it
                            play()
                        }
                    )
                }
            }
        }
    }

    private fun reset() {
        frameCountFlow.value = 0
        prePlayedFrameTotal = 0
        progressFlow.value = 0
        pitchFlow.value = 0f
    }

    @WorkerThread
    private suspend fun play() {
        val uri = uriFlow.value ?: return
        isPlayingFlow.value = true
        startCount++
        val currentStartCount = startCount
        val extractor = MediaExtractor()
        extractor.setDataSource(this, uri, null)
        val (index, format) = (0 until extractor.trackCount).asSequence().map { index ->
            extractor.getTrackFormat(index)
        }.withIndex().find {
            it.value.getString(MediaFormat.KEY_MIME)?.startsWith("audio/") == true
        } ?: return
        extractor.selectTrack(index)

        // 采样率
        val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
        // 通道数
        val channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
        // 编码
        val encoding = format.runCatching {
            getInteger(MediaFormat.KEY_PCM_ENCODING)
        }.getOrDefault(AudioFormat.ENCODING_PCM_16BIT)
        val duration = format.getLong(MediaFormat.KEY_DURATION) / 1_000_000L

        val maxBlockSize = format.getInteger("max-block-size")

        durationFlow.value = duration

        Log.d(TAG, "start play: format = $format currentStartCount = $currentStartCount")

        Log.d(TAG, "start play: currentStartCount = $currentStartCount")

        if (seekToProgress != -1L) {
            prePlayedFrameTotal = seekToProgress * sampleRate
            seekToProgress = -1
        }
        extractor.seekTo(
            (prePlayedFrameTotal / sampleRate * 1_000_000).toLong(),
            MediaExtractor.SEEK_TO_CLOSEST_SYNC
        )

        val mediaCodec =
            MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME) ?: return)
        mediaCodec.configure(format, null, null, 0)
        mediaCodec.start()

        val audioTrack = AudioTrack(
            AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build(),
            AudioFormat.Builder()
                .setEncoding(encoding)
                .setSampleRate(sampleRate)
                .build(),
            AudioTrack.getMinBufferSize(
                sampleRate,
                if (channelCount == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
                encoding
            ),
            AudioTrack.MODE_STREAM,
            AudioManager.AUDIO_SESSION_ID_GENERATE
        )
        audioTrack.play()

        val st = SoundTouch(sampleRate, channelCount)
        st.setTempo(1f)

        val info = MediaCodec.BufferInfo()
        while (isPlayingFlow.value) {
            val ibIndex = mediaCodec.dequeueInputBuffer(10000)
            if (ibIndex >= 0) {
                val buffer = mediaCodec.getInputBuffer(ibIndex) ?: break
                val size = extractor.readSampleData(buffer, 0)
                if (size < 0) {
                    mediaCodec.queueInputBuffer(
                        ibIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM
                    )
                } else {
                    mediaCodec.queueInputBuffer(ibIndex, 0, size, extractor.sampleTime, 0)
                    extractor.advance()
                }
            }
            val obIndex = mediaCodec.dequeueOutputBuffer(info, 10000)
            if (obIndex >= 0) {
                val buffer = mediaCodec.getOutputBuffer(obIndex) ?: break
                if (pitchFlow.value == 0f) {
                    /*buffer.position(info.offset)
                    buffer.limit(info.offset + info.size)*/
                    audioTrack.write(buffer, buffer.remaining(), AudioTrack.WRITE_BLOCKING)
                } else {
                    buffer.order(ByteOrder.LITTLE_ENDIAN)
                    val shortBuffer = buffer.asShortBuffer()
                    val remaining = shortBuffer.remaining()
                    val inArray = ShortArray(remaining)
                    val outArray = ShortArray(info.size)

                    shortBuffer.get(inArray)
                    st.setPitchSemiTones(pitchFlow.value)
                    st.putSamples(inArray, remaining)
                    val size = st.receiveSamples(outArray)
                    audioTrack.write(outArray, 0, size)
                }
                mediaCodec.releaseOutputBuffer(obIndex, false)
            }
            if (currentStartCount != startCount) break
            val headPosition = audioTrack.playbackHeadPosition
            val totalCount = if (ibIndex < 0 && obIndex < 0) {
                delay(100L)
                prePlayedFrameTotal + headPosition + maxBlockSize
            } else prePlayedFrameTotal + headPosition
            frameCountFlow.value = totalCount
            val progress = totalCount / sampleRate
            progressFlow.value = progress
            if (progress >= duration) break
        }
        Log.d(TAG, "stop play: currentStartCount = $currentStartCount startCount = $startCount")
        if (currentStartCount == startCount) {
            prePlayedFrameTotal += audioTrack.playbackHeadPosition
            if (isPlayingFlow.value) {
                reset()
                isPlayingFlow.value = false
            }
        }
        mediaCodec.stop()
        mediaCodec.release()
        extractor.release()
        audioTrack.stop()
        audioTrack.release()
        st.close()
    }
}

@Composable
fun Greeting(
    modifier: Modifier = Modifier,
    singer: String = "",
    songName: String = "",
    frameCount: Long = 0,
    progress: Long = 0L,
    duration: Long = 0L,
    isPlaying: Boolean = false,
    pitch: Float = 0f,
    pitchChanged: (Float) -> Unit = {},
    start: suspend () -> Unit = {},
    stop: () -> Unit = {},
    reset: () -> Unit = {},
    seekTo: suspend (Long) -> Unit = {},
) {
    val scope = rememberCoroutineScope()
    val version by remember {
        mutableStateOf(SoundTouch.getVersionString())
    }

    Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
        Text("$singer - $songName")
        Text("Version = $version")
        Text("Pitch = $pitch")
        Text("FrameCount = $frameCount")
        Button(onClick = {
            scope.launch(Dispatchers.IO) {
                if (isPlaying) {
                    stop()
                } else {
                    start()
                }
            }
        }) {
            Text(if (isPlaying) "Stop" else "Start")
        }
        if (!isPlaying && progress != 0L) {
            Button(onClick = {
                scope.launch {
                    reset()
                }
            }) {
                Text("Reset")
            }
        }
        Row(verticalAlignment = Alignment.CenterVertically) {
            Text("音调调节：")
            Slider(
                valueRange = -5f..5f,
                value = pitch,
                onValueChange = pitchChanged
            )
        }
        if (duration != 0L) {
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text("播放进度：$progress/$duration")
                var sliderValue by remember { mutableFloatStateOf(0f) }
                val source = remember { MutableInteractionSource() }
                val dragging by source.collectIsDraggedAsState()
                Slider(
                    valueRange = 0f..duration.toFloat(),
                    value = if (dragging) sliderValue else progress.toFloat(),
                    onValueChange = { sliderValue = it },
                    onValueChangeFinished = {
                        scope.launch(Dispatchers.IO) {
                            seekTo(sliderValue.toLong())
                        }
                    },
                    interactionSource = source
                )
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    SoundTouchAndroidTheme {
        Greeting()
    }
}